home *** CD-ROM | disk | FTP | other *** search
/ Java 1996 August / Java - Summer 1996.iso / betaclasses / animator.java < prev    next >
Text File  |  1996-01-31  |  22KB  |  891 lines

  1. /*
  2.  * Copyright (c) 1994 Sun Microsystems, Inc. All Rights Reserved.
  3.  *
  4.  * Permission to use, copy, modify, and distribute this software
  5.  * and its documentation for NON-COMMERCIAL purposes and without
  6.  * fee is hereby granted provided that this copyright notice
  7.  * appears in all copies. Please refer to the file "copyright.html"
  8.  * for further important copyright and licensing information.
  9.  *
  10.  * SUN MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF
  11.  * THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
  12.  * TO THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
  13.  * PARTICULAR PURPOSE, OR NON-INFRINGEMENT. SUN SHALL NOT BE LIABLE FOR
  14.  * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR
  15.  * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES.
  16.  */
  17. /*
  18.  * @(#)Animator.java    1.1 95/09/08 Herb Jellinek
  19.  *
  20.  * Copyright (c) 1995 Sun Microsystems, Inc. All Rights Reserved.
  21.  *
  22.  * Permission to use, copy, modify, and distribute this software
  23.  * and its documentation for NON-COMMERCIAL purposes and without
  24.  * fee is hereby granted provided that this copyright notice
  25.  * appears in all copies. Please refer to the file "copyright.html"
  26.  * for further important copyright and licensing information.
  27.  *
  28.  * SUN MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF
  29.  * THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
  30.  * TO THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
  31.  * PARTICULAR PURPOSE, OR NON-INFRINGEMENT. SUN SHALL NOT BE LIABLE FOR
  32.  * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR
  33.  * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES.
  34.  */
  35.  
  36. import java.io.InputStream;
  37. import java.awt.*;
  38. import java.awt.image.ImageProducer;
  39. import java.applet.Applet;
  40. import java.applet.AudioClip;
  41. import java.util.Vector;
  42. import java.util.Hashtable;
  43. import java.util.Enumeration;
  44. import java.io.File;
  45. import java.net.URL;
  46. import java.net.MalformedURLException;
  47.  
  48. /**
  49.  * An applet that plays a sequence of images, as a loop or a one-shot.
  50.  * Can have a soundtrack and/or sound effects tied to individual frames.
  51.  *
  52.  * @author Herb Jellinek
  53.  * @version 1.1, 08 Sep 1995
  54.  */
  55.  
  56. public class Animator extends Applet implements Runnable {
  57.     
  58.     /**
  59.      * The images, in display order (Images).
  60.      */
  61.     Vector images = null;
  62.  
  63.     /**
  64.      * Duration of each image (Integers, in milliseconds).
  65.      */
  66.     Hashtable durations = null;
  67.  
  68.     /**
  69.      * Sound effects for each image (AudioClips).
  70.      */
  71.     Hashtable sounds = null;
  72.  
  73.     /**
  74.      * Position of each image (Points).
  75.      */
  76.     Hashtable positions = null;
  77.  
  78.     /**
  79.      * Background image URL, if any.
  80.      */
  81.     URL backgroundImageURL = null;
  82.  
  83.     /**
  84.      * Background image, if any.
  85.      */
  86.     Image backgroundImage = null;
  87.  
  88.     /**
  89.      * Start-up image URL, if any.
  90.      */
  91.     URL startUpImageURL = null;
  92.  
  93.     /**
  94.      * Start-up image, if any.
  95.      */
  96.     Image startUpImage = null;
  97.  
  98.     /**
  99.      * The soundtrack's URL.
  100.      */
  101.     URL soundtrackURL = null;
  102.  
  103.     /**
  104.      * The soundtrack.
  105.      */
  106.     AudioClip soundtrack;
  107.  
  108.     /**
  109.      * Largest width.
  110.      */
  111.     int maxWidth = 0;
  112.  
  113.     /**
  114.      * Largest height.
  115.      */
  116.     int maxHeight = 0;
  117.  
  118.     /**
  119.      * Was there a problem loading the current image?
  120.      */
  121.     boolean imageLoadError = false;
  122.  
  123.     /**
  124.      * The directory or URL from which the images are loaded
  125.      */
  126.     URL imageSource = null;
  127.  
  128.     /**
  129.      * The directory or URL from which the sounds are loaded
  130.      */
  131.     URL soundSource = null;
  132.  
  133.     /**
  134.      * The thread animating the images.
  135.      */
  136.     Thread engine = null;
  137.  
  138.     /**
  139.      * The current loop slot - index into 'images.'
  140.      */
  141.     int frameNum;
  142.  
  143.     /**
  144.      * frameNum as an Object - suitable for use as a Hashtable key.
  145.      */
  146.     Integer frameNumKey;
  147.     
  148.     /**
  149.      * The current X position (for painting).
  150.      */
  151.     int xPos = 0;
  152.     
  153.     /**
  154.      * The current Y position (for painting).
  155.      */
  156.     int yPos = 0;
  157.     
  158.     /**
  159.      * The default number of milliseconds to wait between frames.
  160.      */
  161.     public static final int defaultPause = 3900;
  162.     
  163.     /**
  164.      * The global delay between images, which can be overridden by
  165.      * the PAUSE parameter.
  166.      */
  167.     int globalPause = defaultPause;
  168.  
  169.     /**
  170.      * Whether or not the thread has been paused by the user.
  171.      */
  172.     boolean userPause = false;
  173.  
  174.     /**
  175.      * Repeat the animation?  If false, just play it once.
  176.      */
  177.     boolean repeat;
  178.  
  179.     /**
  180.      * Load all images before starting display, or do it asynchronously?
  181.      */
  182.     boolean loadFirst;
  183.     
  184.     /**
  185.      * The offscreen image, used in double buffering
  186.      */
  187.     Image offScrImage;
  188.  
  189.     /**
  190.      * The offscreen graphics context, used in double buffering
  191.      */
  192.     Graphics offScrGC;
  193.  
  194.     /**
  195.      * Can we paint yet?
  196.      */
  197.     boolean loaded = false;
  198.  
  199.     /**
  200.      * Was there an initialization error?
  201.      */
  202.     boolean error = false;
  203.  
  204.     /**
  205.      * What we call an image file in messages.
  206.      */
  207.     final static String imageLabel = "image";
  208.     
  209.     /**
  210.      * What we call a sound file in messages.
  211.      */
  212.     final static String soundLabel = "sound";
  213.     
  214.     /**
  215.      * Print silly debugging info?
  216.      */
  217.     boolean debug = false;
  218.  
  219.     /**
  220.      * Info.
  221.      */
  222.     public String getAppletInfo() {
  223.     return "Animator by Herb Jellinek";
  224.     }
  225.  
  226.     /**
  227.      * Parameter Info
  228.      */
  229.     public String[][] getParameterInfo() {
  230.     String[][] info = {
  231.         {"imagesource",     "url",         "a directory"},
  232.         {"startup",     "url",         "displayed at startup"},
  233.         {"background",     "url",         "displayed as background"},
  234.         {"startimage",     "int",         "start index"},
  235.         {"endimage",     "int",         "end index"},
  236.         {"pause",             "int",         "milliseconds"},
  237.         {"pauses",             "ints",     "milliseconds"},
  238.         {"repeat",             "boolean",     "repeat or not"},
  239.         {"positions",    "coordinates",     "path"},
  240.         {"soundsource",    "url",         "audio directory"},
  241.         {"soundtrack",    "url",         "background music"},
  242.         {"sounds",        "urls",        "audio samples"},
  243.     };
  244.     return info;
  245.     }
  246.  
  247.     /**
  248.      * Print silly debugging info.
  249.      */
  250.     void dbg(String s) {
  251.     if (debug) {
  252.         System.out.println(s);
  253.     }
  254.     }
  255.  
  256.     final int setFrameNum(int newFrameNum) {
  257.     frameNumKey = new Integer(frameNum = newFrameNum);
  258.     return frameNum;
  259.     }
  260.     
  261.     public synchronized boolean imageUpdate(Image img, int infoFlags,
  262.                             int x, int y,
  263.                         int width, int height) {
  264.         if ((infoFlags & ERROR) != 0) {
  265.         imageLoadError = true;
  266.     }
  267.  
  268.     notifyAll();
  269.     return true;
  270.     }
  271.  
  272.     void updateMaxDims(Dimension dim) {
  273.     maxWidth = Math.max(dim.width, maxWidth);
  274.     maxHeight = Math.max(dim.height, maxHeight);
  275.     }
  276.  
  277.     /**
  278.      * Parse the IMAGES parameter.  It looks like
  279.      * 1|2|3|4|5, etc., where each number (item) names a source image.
  280.      *
  281.      * @return a Vector of (URL) image file names.
  282.      */
  283.     Vector parseImages(String attr)
  284.     throws MalformedURLException {
  285.     Vector result = new Vector(10);
  286.     for (int i = 0; i < attr.length(); ) {
  287.         int next = attr.indexOf('|', i);
  288.         if (next == -1) next = attr.length();
  289.         String file = attr.substring(i, next);
  290.         result.addElement(new URL(imageSource, "T"+file+".gif"));
  291.         i = next + 1;
  292.     }
  293.     return result;
  294.     }
  295.  
  296.     /**
  297.      * Fetch the images named in the argument, updating 
  298.      * maxWidth and maxHeight as we go.
  299.      * Is restartable.
  300.      *
  301.      * @param images a Vector of URLs
  302.      * @return URL of the first bogus file we hit, null if OK.
  303.      */
  304.     URL fetchImages(Vector images) {
  305.     for (int i = 0; i < images.size(); i++) {
  306.         Object o = images.elementAt(i);
  307.         if (o instanceof URL) {
  308.         URL url = (URL)o;
  309.         tellLoadingMsg(url, imageLabel);
  310.         Image im = getImage(url);
  311.         try {
  312.             updateMaxDims(getImageDimensions(im));
  313.         } catch (Exception e) {
  314.             return url;
  315.         }
  316.         images.setElementAt(im, i);
  317.         }
  318.     }
  319.     return null;
  320.     }
  321.  
  322.     /**
  323.      * Parse the SOUNDS parameter.  It looks like
  324.      * train.au||hello.au||stop.au, etc., where each item refers to a
  325.      * source image.  Empty items mean that the corresponding image
  326.      * has no associated sound.
  327.      *
  328.      * @return a Hashtable of SoundClips keyed to Integer frame numbers.
  329.      */
  330.     Hashtable parseSounds(String attr, Vector images)
  331.     throws MalformedURLException {
  332.     Hashtable result = new Hashtable();
  333.  
  334.     int imageNum = 0;
  335.     int numImages = images.size();
  336.     for (int i = 0; i < attr.length(); ) {
  337.         if (imageNum >= numImages) break;
  338.         
  339.         int next = attr.indexOf('|', i);
  340.         if (next == -1) next = attr.length();
  341.         
  342.         String sound = attr.substring(i, next);
  343.         if (sound.length() != 0) {
  344.         result.put(new Integer(imageNum),
  345.                new URL(soundSource, sound));
  346.         }
  347.         i = next + 1;
  348.         imageNum++;
  349.     }
  350.  
  351.     return result;
  352.     }
  353.  
  354.     /**
  355.      * Fetch the sounds named in the argument.
  356.      * Is restartable.
  357.      *
  358.      * @return URL of the first bogus file we hit, null if OK.
  359.      */
  360.     URL fetchSounds(Hashtable sounds) {
  361.     for (Enumeration e = sounds.keys() ; e.hasMoreElements() ;) {
  362.         Integer num = (Integer)e.nextElement();
  363.         Object o = sounds.get(num);
  364.         if (o instanceof URL) {
  365.         URL file = (URL)o;
  366.         tellLoadingMsg(file, soundLabel);
  367.         try {
  368.             sounds.put(num, getAudioClip(file));
  369.         } catch (Exception ex) {
  370.             return file;
  371.         }
  372.         }
  373.     }
  374.     return null;
  375.     }
  376.  
  377.     /**
  378.      * Parse the PAUSES parameter.  It looks like
  379.      * 1000|500|||750, etc., where each item corresponds to a
  380.      * source image.  Empty items mean that the corresponding image
  381.      * has no special duration, and should use the global one.
  382.      *
  383.      * @return a Hashtable of Integer pauses keyed to Integer
  384.      * frame numbers.
  385.      */
  386.     Hashtable parseDurations(String attr, Vector images) {
  387.     Hashtable result = new Hashtable();
  388.  
  389.     int imageNum = 0;
  390.     int numImages = images.size();
  391.     for (int i = 0; i < attr.length(); ) {
  392.         if (imageNum >= numImages) break;
  393.         
  394.         int next = attr.indexOf('|', i);
  395.         if (next == -1) next = attr.length();
  396.  
  397.         if (i != next - 1) {
  398.         int duration = Integer.parseInt(attr.substring(i, next));
  399.         result.put(new Integer(imageNum), new Integer(duration));
  400.         } else {
  401.         result.put(new Integer(imageNum),
  402.                new Integer(globalPause));
  403.         }
  404.         i = next + 1;
  405.         imageNum++;
  406.     }
  407.  
  408.     return result;
  409.     }
  410.  
  411.     /**
  412.      * Parse a String of form xxx@yyy and return a Point.
  413.      */
  414.     Point parsePoint(String s) throws ParseException {
  415.     int atPos = s.indexOf('@');
  416.     if (atPos == -1) throw new ParseException("Illegal position: "+s);
  417.     return new Point(Integer.parseInt(s.substring(0, atPos)),
  418.              Integer.parseInt(s.substring(atPos + 1)));
  419.     }
  420.  
  421.  
  422.     /**
  423.      * Parse the POSITIONS parameter.  It looks like
  424.      * 10@30|11@31|||12@20, etc., where each item is an X@Y coordinate
  425.      * corresponding to a source image.  Empty items mean that the
  426.      * corresponding image has the same position as the preceding one.
  427.      *
  428.      * @return a Hashtable of Points keyed to Integer frame numbers.
  429.      */
  430.     Hashtable parsePositions(String param, Vector images)
  431.     throws ParseException {
  432.     Hashtable result = new Hashtable();
  433.  
  434.     int imageNum = 0;
  435.     int numImages = images.size();
  436.     for (int i = 0; i < param.length(); ) {
  437.         if (imageNum >= numImages) break;
  438.         
  439.         int next = param.indexOf('|', i);
  440.         if (next == -1) next = param.length();
  441.  
  442.         if (i != next) {
  443.         result.put(new Integer(imageNum),
  444.                parsePoint(param.substring(i, next)));
  445.         }
  446.         i = next + 1;
  447.         imageNum++;
  448.     }
  449.  
  450.     return result;
  451.     }
  452.     
  453.     /**
  454.      * Get the dimensions of an image.
  455.      * @return the image's dimensions.
  456.      */
  457.     synchronized Dimension getImageDimensions(Image im)
  458.     throws ImageNotFoundException {
  459.     // Get the width of the image.
  460.     int width;
  461.     int height;
  462.     
  463.     while ((width = im.getWidth(this)) < 0) {
  464.         try {
  465.         wait();
  466.         } catch (InterruptedException e) { }
  467.         if (imageLoadError) {
  468.         throw new ImageNotFoundException(im.getSource());
  469.         }
  470.     }
  471.     
  472.     // Get the height of the image.
  473.     while ((height = im.getHeight(this)) < 0) {
  474.         try {
  475.         wait();
  476.         } catch (InterruptedException e) { }
  477.         if (imageLoadError) {
  478.         throw new ImageNotFoundException(im.getSource());
  479.         }
  480.     }
  481.  
  482.     return new Dimension(width, height);
  483.     }
  484.  
  485.     /**
  486.      * Stuff a range of image names into a Vector.
  487.      * @return a Vector of image URLs.
  488.      */
  489.     Vector prepareImageRange(int startImage, int endImage)
  490.     throws MalformedURLException {
  491.     Vector result = new Vector(Math.abs(endImage - startImage) + 1);
  492.     if (startImage > endImage) {
  493.         for (int i = startImage; i >= endImage; i--) {
  494.         result.addElement(new URL(imageSource, "T"+i+".gif"));
  495.         }
  496.     } else {
  497.         for (int i = startImage; i <= endImage; i++) {
  498.         result.addElement(new URL(imageSource, "T"+i+".gif"));
  499.         }
  500.     }
  501.     return result;
  502.     }
  503.  
  504.     
  505.     /**
  506.      * Initialize the applet.  Get parameters.
  507.      */
  508.     public void init() {
  509.  
  510.     try {
  511.         String param = getParameter("IMAGESOURCE");    
  512.         imageSource = (param == null) ? getDocumentBase() : new URL(getDocumentBase(), param + "/");
  513.         dbg("IMAGESOURCE = "+param);
  514.     
  515.         param = getParameter("PAUSE");
  516.         globalPause =
  517.         (param != null) ? Integer.parseInt(param) : defaultPause;
  518.         dbg("PAUSE = "+param);
  519.  
  520.         param = getParameter("REPEAT");
  521.         repeat = (param == null) ? true : (param.equalsIgnoreCase("yes") ||
  522.                            param.equalsIgnoreCase("true"));
  523.  
  524.         int startImage = 1;
  525.         int endImage = 1;
  526.         param = getParameter("ENDIMAGE");
  527.         dbg("ENDIMAGE = "+param);
  528.         if (param != null) {
  529.         endImage = Integer.parseInt(param);
  530.         param = getParameter("STARTIMAGE");
  531.         dbg("STARTIMAGE = "+param);
  532.         if (param != null) {
  533.             startImage = Integer.parseInt(param);
  534.         }
  535.         images = prepareImageRange(startImage, endImage);
  536.         } else {
  537.         param = getParameter("STARTIMAGE");
  538.         dbg("STARTIMAGE = "+param);
  539.         if (param != null) {
  540.             startImage = Integer.parseInt(param);
  541.             images = prepareImageRange(startImage, endImage);
  542.         } else {
  543.             param = getParameter("IMAGES");
  544.             dbg("IMAGES = "+param);
  545.             if (param == null) {
  546.             showStatus("No legal IMAGES, STARTIMAGE, or ENDIMAGE "+
  547.                    "specified.");
  548.             return;
  549.             } else {
  550.             images = parseImages(param);
  551.             }
  552.         }
  553.         }
  554.  
  555.         param = getParameter("BACKGROUND");
  556.         dbg("BACKGROUND = "+param);
  557.         if (param != null) {
  558.         backgroundImageURL = new URL(imageSource, param);
  559.         }
  560.  
  561.         param = getParameter("STARTUP");
  562.         dbg("STARTUP = "+param);
  563.         if (param != null) {
  564.         startUpImageURL = new URL(imageSource, param);
  565.         }
  566.  
  567.         param = getParameter("SOUNDSOURCE");
  568.         soundSource = (param == null) ? imageSource : new URL(getDocumentBase(), param + "/");
  569.         dbg("SOUNDSOURCE = "+param);
  570.     
  571.         param = getParameter("SOUNDS");
  572.         dbg("SOUNDS = "+param);
  573.         if (param != null) {
  574.         sounds = parseSounds(param, images);
  575.         }
  576.  
  577.         param = getParameter("PAUSES");
  578.         dbg("PAUSES = "+param);
  579.         if (param != null) {
  580.         durations = parseDurations(param, images);
  581.         }
  582.  
  583.         param = getParameter("POSITIONS");
  584.         dbg("POSITIONS = "+param);
  585.         if (param != null) {
  586.         positions = parsePositions(param, images);
  587.         }
  588.  
  589.         param = getParameter("SOUNDTRACK");
  590.         dbg("SOUNDTRACK = "+param);
  591.         if (param != null) {
  592.         soundtrackURL = new URL(soundSource, param);
  593.         }
  594.     } catch (MalformedURLException e) {
  595.         showParseError(e);
  596.     } catch (ParseException e) {
  597.         showParseError(e);
  598.     }
  599.     
  600.  
  601.  
  602.     setFrameNum(0);
  603.     }
  604.  
  605.     void tellLoadingMsg(String file, String fileType) {
  606.     showStatus("Animator: loading "+fileType+" "+abridge(file, 20));
  607.     }
  608.  
  609.     void tellLoadingMsg(URL url, String fileType) {
  610.     tellLoadingMsg(url.toExternalForm(), fileType);
  611.     }
  612.  
  613.     void clearLoadingMessage() {
  614.     showStatus("");
  615.     }
  616.     
  617.     /**
  618.      * Cut the string down to length=len, while still keeping it readable.
  619.      */
  620.     static String abridge(String s, int len) {
  621.     String ellipsis = "...";
  622.  
  623.     if (len >= s.length()) {
  624.         return s;
  625.     }
  626.  
  627.     int trim = len - ellipsis.length();
  628.     return s.substring(0, trim / 2)+ellipsis+
  629.         s.substring(s.length() - trim / 2);
  630.     }
  631.     
  632.     void loadError(URL badURL, String fileType) {
  633.     String errorMsg = "Animator: Couldn't load "+fileType+" "+
  634.         badURL.toExternalForm();
  635.     showStatus(errorMsg);
  636.     System.err.println(errorMsg);
  637.     error = true;
  638.     repaint();
  639.     }
  640.  
  641.     void showParseError(Exception e) {
  642.     String errorMsg = "Animator: Parse error: "+e;
  643.     showStatus(errorMsg);
  644.     System.err.println(errorMsg);
  645.     error = true;
  646.     repaint();
  647.     }
  648.  
  649.     void startPlaying() {
  650.     if (soundtrack != null) {
  651.         soundtrack.loop();
  652.     }
  653.     }
  654.  
  655.     void stopPlaying() {
  656.     if (soundtrack != null) {
  657.         soundtrack.stop();
  658.     }
  659.     }
  660.  
  661.     /**
  662.      * Run the animation. This method is called by class Thread.
  663.      * @see java.lang.Thread
  664.      */
  665.     public void run() {
  666.     Thread me = Thread.currentThread();
  667.  
  668.     me.setPriority(Thread.MIN_PRIORITY);
  669.  
  670.     if (! loaded) {
  671.         try {
  672.         // ... to do a bunch of loading.
  673.         if (startUpImageURL != null) {
  674.             tellLoadingMsg(startUpImageURL, imageLabel);
  675.             startUpImage = getImage(startUpImageURL);
  676.             try {
  677.             updateMaxDims(getImageDimensions(startUpImage));
  678.             } catch (Exception e) {
  679.             loadError(startUpImageURL, "start-up image");
  680.             }
  681.             resize(maxWidth, maxHeight);
  682.             repaint();
  683.         }
  684.  
  685.         if (backgroundImageURL != null) {
  686.             tellLoadingMsg(backgroundImageURL, imageLabel);
  687.             backgroundImage = getImage(backgroundImageURL);
  688.             repaint();
  689.             try {
  690.             updateMaxDims(
  691.                getImageDimensions(backgroundImage));
  692.             } catch (Exception e) {
  693.             loadError(backgroundImageURL, "background image");
  694.             }
  695.         }
  696.  
  697.         URL badURL = fetchImages(images);
  698.         if (badURL != null) {
  699.             loadError(badURL, imageLabel);
  700.             return;
  701.         }
  702.  
  703.         if (soundtrackURL != null && soundtrack == null) {
  704.             tellLoadingMsg(soundtrackURL, imageLabel);
  705.             soundtrack = getAudioClip(soundtrackURL);
  706.             if (soundtrack == null) {
  707.             loadError(soundtrackURL, "soundtrack");
  708.             return;
  709.             }
  710.         }
  711.  
  712.         if (sounds != null) {
  713.             badURL = fetchSounds(sounds);
  714.             if (badURL != null) {
  715.             loadError(badURL, soundLabel);
  716.             return;
  717.             }
  718.         }
  719.  
  720.         clearLoadingMessage();
  721.  
  722.         offScrImage = createImage(maxWidth, maxHeight);
  723.         offScrGC = offScrImage.getGraphics();
  724.         offScrGC.setColor(Color.lightGray);
  725.  
  726.         resize(maxWidth, maxHeight);
  727.         loaded = true;
  728.         error = false;
  729.         } catch (Exception e) {
  730.         error = true;
  731.         e.printStackTrace();
  732.         }
  733.     }
  734.  
  735.     if (userPause) {
  736.         return;
  737.     }
  738.  
  739.     if (repeat || frameNum < images.size()) {
  740.         startPlaying();
  741.     }
  742.  
  743.     try {
  744.         if (images.size() > 1) {
  745.         while (maxWidth > 0 && maxHeight > 0 && engine == me) {
  746.             if (frameNum >= images.size()) {
  747.             if (!repeat) {
  748.                 return;
  749.             }
  750.             setFrameNum(0);
  751.             }
  752.             repaint();
  753.  
  754.             if (sounds != null) {
  755.             AudioClip clip =
  756.                 (AudioClip)sounds.get(frameNumKey);
  757.             if (clip != null) {
  758.                 clip.play();
  759.             }
  760.             }
  761.  
  762.             try {
  763.             Integer pause = null;
  764.             if (durations != null) {
  765.                 pause = (Integer)durations.get(frameNumKey);
  766.             }
  767.             if (pause == null) {
  768.                 Thread.sleep(globalPause);
  769.             } else {
  770.                 Thread.sleep(pause.intValue());
  771.             }
  772.             } catch (InterruptedException e) {
  773.             // Should we do anything?
  774.             }
  775.             setFrameNum(frameNum+1);
  776.         }
  777.         }
  778.     } finally {
  779.         stopPlaying();
  780.     }
  781.     }
  782.  
  783.     /**
  784.      * Paint the current frame.
  785.      */
  786.     public void paint(Graphics g) {
  787.     if (error || !loaded) {
  788.         if (startUpImage != null) {
  789.         g.drawImage(startUpImage, 0, 0, this);
  790.         } else {
  791.         if (backgroundImage != null) {
  792.             g.drawImage(backgroundImage, 0, 0, this);
  793.         } else {
  794.             g.clearRect(0, 0, maxWidth, maxHeight);
  795.         }
  796.         }
  797.     } else {
  798.         if ((images != null) && (images.size() > 0)) {
  799.         if (frameNum < images.size()) {
  800.             if (backgroundImage == null) {
  801.             offScrGC.fillRect(0, 0, maxWidth, maxHeight);
  802.             } else {
  803.             offScrGC.drawImage(backgroundImage, 0, 0, this);
  804.             }
  805.  
  806.             Image image = (Image)images.elementAt(frameNum);
  807.             Point pos = null;
  808.             if (positions != null) {
  809.             pos = (Point)positions.get(frameNumKey);
  810.             }
  811.             if (pos != null) {
  812.             xPos = pos.x;
  813.             yPos = pos.y;
  814.             }
  815.             offScrGC.drawImage(image, xPos, yPos, this);
  816.             g.drawImage(offScrImage, 0, 0, this);
  817.         } else {
  818.             // no more animation, but need to draw something
  819.             dbg("No more animation; drawing last image.");
  820.             g.drawImage((Image)images.lastElement(), 0, 0, this);
  821.         }
  822.         }
  823.     }
  824.     }
  825.  
  826.     /**
  827.      * Start the applet by forking an animation thread.
  828.      */
  829.     public void start() {
  830.     if (engine == null) {
  831.         engine = new Thread(this);
  832.         engine.start();
  833.     }
  834.     }
  835.  
  836.     /**
  837.      * Stop the insanity, um, applet.
  838.      */
  839.     public void stop() {
  840.     if (engine != null && engine.isAlive()) {
  841.         engine.stop();
  842.     }
  843.     engine = null;
  844.     }
  845.  
  846.     /**
  847.      * Pause the thread when the user clicks the mouse in the applet.
  848.      * If the thread has stopped (as in a non-repeat performance),
  849.      * restart it.
  850.      */
  851.     public boolean handleEvent(Event evt) {
  852.     if (evt.id == Event.MOUSE_DOWN) {
  853.         if (loaded) {
  854.         if (engine != null && engine.isAlive()) {
  855.             if (userPause) {
  856.             engine.resume();
  857.             startPlaying();
  858.             } else {
  859.             engine.suspend();
  860.             stopPlaying();
  861.             }
  862.             userPause = !userPause;
  863.         } else {
  864.             userPause = false;
  865.             setFrameNum(0);
  866.             engine = new Thread(this);
  867.             engine.start();
  868.         }
  869.         }
  870.         return true;
  871.     } else {        
  872.         return super.handleEvent(evt);
  873.     }
  874.     }
  875.     
  876. }
  877.  
  878.  
  879. class ParseException extends Exception {
  880.     ParseException(String s) {
  881.     super(s);
  882.     }
  883. }
  884.  
  885. class ImageNotFoundException extends Exception {
  886.     ImageNotFoundException(ImageProducer source) {
  887.     super(source+"");
  888.     }
  889. }
  890.  
  891.